package org.jdsm.flow.core.users.impl.gae.objectify.dao;


import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.cmd.Query;
import org.jdsm.flow.core.users.IGroup;
import org.jdsm.flow.core.users.IRole;
import org.jdsm.flow.core.users.IUser;
import org.jdsm.flow.core.users.dao.IUserDao;
import org.jdsm.flow.core.users.impl.gae.objectify.Group;
import org.jdsm.flow.core.users.impl.gae.objectify.Role;
import org.jdsm.flow.core.users.impl.gae.objectify.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.util.*;

/**
 * User: mikom
 * Date: 11/27/12
 * Time: 4:43 PM
 */
public class UserDAO implements IUserDao<Long> {

    private static final Logger log = LoggerFactory.getLogger(UserDAO.class);

    @Resource
    private ObjectifyFactory objectifyFactory;

    private Objectify ofy() {
        return objectifyFactory.begin();
    }

    @Override
    public IUser findUserById(Long identifier) {
        if(identifier == null){
            log.warn("user id can't be null");
            return null;
        }
        return ofy().load().type(User.class).id(identifier).get();
    }

    @Override
    public IUser loadUser(IUser user) {
        if(user == null){
            log.warn("user can't be null");
            return null;
        }
        if(user.getId() == null){
            log.warn("user's identifier can't be null");
            return null;
        }
        user = ofy().load().entity(user).get();
        log.debug("user {} was loaded", user);
        return user;
    }

    @Override
    public Collection<IRole> findUserRolesByUserId(Long identifier) {
        if(identifier == null){
            log.warn("identifier can't be null");
            return Collections.emptyList();
        }
        User user = ofy().load().type(User.class).id(identifier).get();
        if(user == null){
            log.debug("can't find user by id: {}", identifier);
            return Collections.emptyList();
        }
        ofy().load().refs(user.getRoles());
        Collection<IRole> roles = new ArrayList<IRole>();
        for (Ref<Role> r : user.getRoles()) {
            roles.add(r.get());
        }
        return roles;
    }

    @Override
    public Collection<IGroup> findUserGroupsByUserId(Long identifier) {
        if(identifier == null){
            log.warn("identifier should be not null");
            return Collections.emptyList();
        }
        User user = ofy().load().type(User.class).id(identifier).get();
        if(user == null){
            log.debug("could not find user by id: {}", identifier);
            return Collections.emptyList();
        }
        ofy().load().refs(user.getGroups());
        Collection<IGroup> groups = new ArrayList<IGroup>();
        for (Ref<Group> g : user.getGroups()) {
            groups.add(g.get());
        }
        return groups;
    }

    @Override
    public IRole findRoleById(Long id) {
        if(id == null){
            log.warn("role's identifier can't be null");
            return null;
        }
        Ref<Role> ref = ofy().load().type(Role.class).id(id);
        if(ref == null){
            log.warn("can't find role by id:{}", id);
            return null;
        }
        return ref.get();
    }

    @Override
    public IRole loadRole(IRole role) {
        if(role == null){
            log.warn("role can't be null");
            return null;
        }
        if(role.getId() == null){
            log.warn("instance role should contain property id unlike null");
            return null;
        }
        role = ofy().load().entity(role).get();
        log.debug("role {} was loaded", role);
        return role;
    }

    @Override
    public Collection<IUser> findUsersForRole(IRole role) {
        if(role == null){
            log.warn("role can't be null");
            return Collections.emptyList();
        }
        if(role.getId() == null){
            log.warn("instance role should contain property id unlike null");
            return Collections.emptyList();
        }
        Role r = ofy().load().type(Role.class).id(((Role) role).getId()).get();
        if(r == null){
            log.warn("could not find role : {}", role);
            return Collections.emptyList();
        }
        ofy().load().refs(r.getUsers());
        Collection<IUser> users = new HashSet<IUser>();
        for (Ref<User> u : r.getUsers()) {
            users.add(u.get());
        }
        return users;
    }

    @Override
    public IGroup findGroupById(Long id) {
        if(id == null){
            log.warn("group's id should be not null");
            return null;
        }
        Ref<Group> ref = ofy().load().type(Group.class).id(id);
        if(ref == null){
            log.warn("can't find group by id: {}", id);
            return null;
        }
        return ref.get();
    }

    @Override
    public IGroup loadGroup(IGroup group) {
        if(group == null){
            log.warn("group can't be null");
            return null;
        }
        if(group.getId() == null){
            log.warn("group's identifier should be not null");
            return null;
        }
        group = ofy().load().entity(group).get();
        log.debug("group {} was loaded");
        return group;
    }

    @Override
    public Collection<IUser> findUsersForGroup(IGroup group) {
        if(group == null){
            log.warn("group can't be null");
            return Collections.emptySet();
        }
        if(group.getId() == null){
            log.warn("group's id can't be null");
            return Collections.emptyList();
        }
        Group g = ofy().load().type(Group.class).id(((Group) group).getId()).get();
        if(g == null){
            log.debug("could not find group: {}", group);
            return Collections.emptyList();
        }
        ofy().load().refs(g.getUsers());
        Collection<IUser> users = new HashSet<IUser>();
        for (Ref<User> u : g.getUsers()) {
            users.add(u.get());
        }
        return users;
    }

    @Override
    public Collection<IUser> loadUsersByCriteria(int maxResult, int startResult, boolean isSort, String field) {
        Query<User> query = ofy().load().type(User.class).offset(startResult).limit(maxResult);
        if (field != null && !"".equals(field)) {
            String prefix = "";
            if (!isSort) prefix = "-";
            query.order(prefix + field);
        }
        return new ArrayList<IUser>(query.list());
    }

    @Override
    public Collection<IGroup> loadGroupsByCriteria(int maxResult, int startResult, boolean isSort, String field) {
        Query<Group> query = ofy().load().type(Group.class).offset(startResult).limit(maxResult);
        if (field != null && !"".equals(field)) {
            String prefix = "";
            if (!isSort) prefix = "-";
            query.order(prefix + field);
        }
        return new ArrayList<IGroup>(query.list());
    }

    @Override
    public Collection<IRole> loadRolesByCriteria(int maxResult, int startResult, boolean isSort, String field) {
        Query<Role> query = ofy().load().type(Role.class).offset(startResult).limit(maxResult);
        if (field != null && !"".equals(field)) {
            String prefix = "";
            if (!isSort) prefix = "-";
            query.order(prefix + field);
        }
        return new ArrayList<IRole>(query.list());
    }

    @Override
    public IUser removeUser(Long id) {
        IUser user = ofy().load().type(User.class).id(id).get();
        ofy().delete().entity(user)
        //.now()
        ;
        return user;
    }

    @Override
    public IRole removeRole(Long id) {
        Role role = ofy().load().type(Role.class).id(id).get();
        ofy().delete().entity(role)
        //.now()
        ;
        return role;
    }

    @Override
    public IGroup removeGroup(Long id) {
        Group group = ofy().load().type(Group.class).id(id).get();
        ofy().delete().entity(group)
        //.now()
        ;
        return group;
    }

    @Override
    public void includeUserToRoles(IUser user, Collection<Long> roleIds) {

        Ref<User> refUser = ofy().load().type(User.class).id(((User) user).getId());
        User u = ofy().load().ref(refUser).get();

        ofy().load().refs(u.getRoles());
        List<Ref<Role>> uRoles = u.getRoles();

        for (Long id : roleIds) {
            Ref<Role> refRole = ofy().load().type(Role.class).id(id);
            uRoles.add(refRole);

            Role role = ofy().load().ref(refRole).get();

            ofy().load().refs(role.getUsers());
            role.getUsers().add(refUser);
            ofy().save().entity(role);
        }

        ofy().save().entity(u);

    }

    @Override
    public void includeUserToGroups(IUser user, Collection<Long> groupIds) {
        Ref<User> refUser = ofy().load().type(User.class).id(((User) user).getId());
        User u = ofy().load().ref(refUser).get();

        ofy().load().refs(u.getRoles());
        List<Ref<Group>> uGroups = u.getGroups();

        for (Long id : groupIds) {
            Ref<Group> refGroup = ofy().load().type(Group.class).id(id);
            uGroups.add(refGroup);

            Group group = ofy().load().ref(refGroup).get();

            ofy().load().refs(group.getUsers());
            group.getUsers().add(refUser);

            ofy().save().entity(group);
        }
        ofy().save().entity(u);
    }

    @Override
    public void excludeUserFromRoles(IUser user, Collection<Long> roles) {
        Ref<User> refUser = ofy().load().type(User.class).id(((User) user).getId());
        User u = ofy().load().ref(refUser).get();

        ofy().load().refs(u.getRoles());
        List<Ref<Role>> uRoles = u.getRoles();

        for (Long id : roles) {
            Ref<Role> refRole = ofy().load().type(Role.class).id(id);
            uRoles.remove(refRole);

            Role role = ofy().load().ref(refRole).get();

            ofy().load().refs(role.getUsers());
            role.getUsers().remove(refUser);

            ofy().save().entity(role);
        }
        ofy().save().entity(u);
    }

    @Override
    public void excludeUserFromGroups(IUser user, Collection<Long> groups) {
        Ref<User> refUser = ofy().load().type(User.class).id(((User) user).getId());
        User u = ofy().load().ref(refUser).get();

        ofy().load().refs(u.getGroups());
        List<Ref<Group>> uGroups = u.getGroups();

        for (Long id : groups) {
            Ref<Group> refGroup = ofy().load().type(Group.class).id(id);
            uGroups.remove(refGroup);

            Group group = ofy().load().ref(refGroup).get();

            ofy().load().refs(group.getUsers());
            group.getUsers().remove(refUser);
            ofy().save().entity(group);
        }

        ofy().save().entity(u);
    }

    @Override
    public IUser saveOrUpdate(IUser user) {
        Key<IUser> u = ofy().save().entity(user).now();
        user = ofy().load().key(u).get();
        log.debug("user {} was saved", user);
        return user;
    }

    @Override
    public IRole saveOrUpdate(IRole role) {
        Key<IRole> r = ofy().save().entity(role).now();
        role = ofy().load().key(r).get();
        log.debug("role {} was saved", role);
        return role;
    }

    @Override
    public IGroup saveOrUpdate(IGroup group) {
        Key<IGroup> g = ofy().save().entity(group).now();
        group = ofy().load().key(g).get();
        log.debug("group {} was saved", group);
        return group;
    }

    @Override
    public Collection<IGroup> findAllowedGroups(Long permission) {
        List<IGroup> groups = new ArrayList<IGroup>(ofy().load().type(Group.class).filter("permission >", permission).list());

        log.debug("loaded groups:{} by permission:{}", groups, permission);

        return groups;
    }

    @Override
    public Collection<IRole> findAllowedRoles(Long permission) {
        List<IRole> roles = new ArrayList<IRole>(ofy().load().type(Role.class).filter("permission >", permission).list());

        log.debug("loaded roles:{} by permission:{}", roles, permission);

        return roles;
    }

    @Override
    public boolean isAllowedUserAccess(IUser user, IGroup group) {
        if (group == null || group.getPermission() == null) {
            log.warn("group can't be null");
            return false;
        }

        if(user == null){
            log.warn("user can't be null");
            return false;
        }

        Ref<User> refUser = ofy().load().type(User.class).id(((User) user).getId());

        if (refUser == null) {
            log.warn("can't found user: {}", user);
            return false;
        }


        User u = ofy().load().ref(refUser).get();

        ofy().load().refs(u.getGroups());
        List<Ref<Group>> uGroups = u.getGroups();

        for(Ref<Group> r : uGroups){
            if(r.get().getPermission() >= group.getPermission()){
                log.debug("user :{} has allowed access to group:{}", user, group);
                return true;
            }
        }

        log.debug("user :{} hasn't allowed access to group:{}", user, group);

        return false;
    }

    @Override
    public boolean isAllowedUserAccess(IUser user, IRole role) {
        if (role == null || role.getPermission() == null) {
            log.warn("role can't be null");
            return false;
        }

        if(user == null){
            log.warn("user can't be null");
            return false;
        }

        Ref<User> refUser = ofy().load().type(User.class).id(((User) user).getId());

        if (refUser == null) {
            log.warn("can't found user: {}", user);
            return false;
        }


        User u = ofy().load().ref(refUser).get();

        ofy().load().refs(u.getRoles());
        List<Ref<Role>> uRoles = u.getRoles();

        for(Ref<Role> r : uRoles){
            if(r.get().getPermission() >= role.getPermission()){
                log.debug("user :{} has allowed access to role:{}", user, role);
                return true;
            }
        }

        log.debug("user :{} hasn't allowed access to role:{}", user, role);

        return false;
    }

    @Override
    public IUser findUserByName(String name) {
        List<User> userList = ofy().load().type(User.class).filter("name", name).list();
        if (userList.size() > 1) {
            log.warn("confusion situation. name should be unique in DB. return first value");
            return userList.get(0);
        }
        if (userList.size() <= 0) {
            log.warn("can't find entity by name:{}", name);
            return null;
        }
        return userList.get(0);
    }

    @Override
    public IUser findUserByNickName(String nickName) {
        List<User> userList = ofy().load().type(User.class).filter("nickName", nickName).list();
        if (userList.size() > 1) {
            log.warn("confusion situation. nick name should be unique in DB. return first value");
            return userList.get(0);
        }
        if (userList.size() <= 0) {
            log.warn("can't find entity by nick name:{}", nickName);
            return null;
        }
        return userList.get(0);
    }

    @Override
    public IRole findRoleByName(String name) {
        List<Role> roles = ofy().load().type(Role.class).filter("name", name).list();
        if (roles.size() > 1) {
            Role r = roles.get(0);
            log.warn("confusion situation. role name:{} should be unique in DB, return first value:{}", name, r);
            return r;
        }
        if (roles.size() <= 0) {
            log.warn("not found entity by name:{}", name);
            return null;
        }
        return roles.get(0);
    }

    @Override
    public IGroup findGroupByName(String name) {
        List<Group> groups = ofy().load().type(Group.class).filter("name", name).list();
        if (groups.size() > 1) {
            Group g = groups.get(0);
            log.warn("confusion situation. group name:{} should be unique in DB, return first value:{}", name, g);
            return g;
        }
        if (groups.size() <= 0) {
            log.warn("not found entity by name:{}", name);
            return null;
        }
        return groups.get(0);
    }

    @Override
    public int usersCount() {
        return ofy().load().type(User.class).count();
    }

    @Override
    public int rolesCount() {
        return ofy().load().type(Role.class).count();
    }

    @Override
    public int groupCount() {
        return ofy().load().type(Group.class).count();
    }

    @Override
    public IUser newUserInstance() {
        return new User();
    }

    @Override
    public IRole newRoleInstance() {
        return new Role();
    }

    @Override
    public IGroup newGroupInstance() {
        return new Group();
    }

    @Override
    public void enableUserToRoles(IUser user, Collection<IRole> roles) {
        List<Long> rolesIds = new ArrayList<Long>();
        for (IRole r : roles) {
            rolesIds.add(((Role) r).getId());
        }
        includeUserToRoles(user, rolesIds);
    }

    @Override
    public void disableUserFromRoles(IUser user, Collection<IRole> roles) {
        List<Long> rolesIds = new ArrayList<Long>();
        for (IRole r : roles) {
            rolesIds.add(((Role) r).getId());
        }
        excludeUserFromRoles(user, rolesIds);
    }

    @Override
    public void enableUserToGroups(IUser user, Collection<IGroup> groups) {
        List<Long> groupsIds = new ArrayList<Long>();
        for (IGroup g : groups) {
            groupsIds.add(((Group) g).getId());
        }
        includeUserToGroups(user, groupsIds);
    }

    @Override
    public void disableUserFromGroups(IUser user, Collection<IGroup> groups) {
        List<Long> groupsIds = new ArrayList<Long>();
        for (IGroup g : groups) {
            groupsIds.add(((Group) g).getId());
        }
        excludeUserFromGroups(user, groupsIds);
    }

    @Override
    public void reassignUserRoles(IUser user, Collection<IRole> roles) {
        Ref<User> refUser = ofy().load().type(User.class).id(((User) user).getId());
        User u = ofy().load().ref(refUser).get();

        ofy().load().refs(u.getRoles());
        List<Ref<Role>> uRoles = u.getRoles();

        //remove references on User from Group
        for (Ref<Role> rRole : uRoles) {
            Role role = ofy().load().ref(rRole).get();
            ofy().load().refs(role.getUsers());
            role.getUsers().remove(refUser);
            ofy().save().entity(role);
        }

        //remove references on Group from User
        u.getRoles().clear();


        //assign new Groups to User
        List<Long> rolesIds = new ArrayList<Long>();
        for (IRole r : roles) {
            if (r != null && r.getId() != null)
                rolesIds.add((Long) r.getId());
        }


        Set<Long> rolesKey =
                ofy().load().type(Role.class).ids((Long[]) rolesIds.toArray(new Long[0])).keySet();

//        for (Long id : groups) {
        for (Long id : rolesKey) {
            Ref<Role> refRole = ofy().load().type(Role.class).id(id);
            //assign references Group to User
            uRoles.add(refRole);

            Role role = ofy().load().ref(refRole).get();

            ofy().load().refs(role.getUsers());
            //assign a reference User for each Group
            role.getUsers().add(refUser);
            ofy().save().entity(role);
        }

        ofy().save().entity(u);
    }

    @Override
    public void reassignUserGroups(IUser user, Collection<IGroup> groups) {
        Ref<User> refUser = ofy().load().type(User.class).id(((User) user).getId());
        User u = ofy().load().ref(refUser).get();

        ofy().load().refs(u.getGroups());
        List<Ref<Group>> uGroups = u.getGroups();

        //remove references on User from Group
        for (Ref<Group> rGroup : uGroups) {
            Group group = ofy().load().ref(rGroup).get();
            ofy().load().refs(group.getUsers());
            group.getUsers().remove(refUser);
            ofy().save().entity(group);
        }

        //remove references on Group from User
        u.getGroups().clear();


        //assign new Groups to User
        List<Long> groupIds = new ArrayList<Long>();
        for (IGroup g : groups) {
            if (g != null && g.getId() != null)
                groupIds.add((Long) g.getId());
        }


        Set<Long> groupKeys =
                ofy().load().type(Group.class).ids((Long[]) groupIds.toArray(new Long[0])).keySet();

//        for (Long id : groups) {
        for (Long id : groupKeys) {
            Ref<Group> refGroup = ofy().load().type(Group.class).id(id);
            //assign references Group to User
            uGroups.add(refGroup);

            Group group = ofy().load().ref(refGroup).get();

            ofy().load().refs(group.getUsers());
            //assign a reference User for each Group
            group.getUsers().add(refUser);
            ofy().save().entity(group);
        }

        ofy().save().entity(u);
    }

    @Override
    public void reassignUserRoleById(IUser user, Collection<Long> roleIds) {
        Ref<User> refUser = ofy().load().type(User.class).id(((User) user).getId());
        User u = ofy().load().ref(refUser).get();

        ofy().load().refs(u.getRoles());
        Collection<Ref<Role>> oldRolesForUser = u.getRoles();
        //remove user refs from role entities
        for(Ref<Role> rf : oldRolesForUser){
            ofy().load().ref(rf);
            Role r = rf.get();
            r.getUsers().remove(refUser);
        }

        Collection<Role> roles = ofy().load().type(Role.class).ids(roleIds).values();
        //clean user roles refs
        u.getRoles().clear();

        //add new refs for user
        for(Role r : roles){
            r.getUsers().add(refUser);
            Ref<Role> refRole = ofy().load().type(Role.class).id(u.getId());
            u.getRoles().add(refRole);
        }
        ofy().save().entities(roles);
        ofy().save().entity(u);

    }

    @Override
    public void reassignUserGroupById(IUser user, Collection<Long> groupIds) {
        Ref<User> refUser = ofy().load().type(User.class).id(((User) user).getId());
        User u = ofy().load().ref(refUser).get();

        ofy().load().refs(u.getGroups());
        Collection<Ref<Group>> oldGroupsForUser = u.getGroups();
        //remove user refs from role entities
        for(Ref<Group> gf : oldGroupsForUser){
            ofy().load().ref(gf);
            Group r = gf.get();
            r.getUsers().remove(refUser);
        }

        Collection<Group> groups = ofy().load().type(Group.class).ids(groupIds).values();
        //clean user roles refs
        u.getRoles().clear();

        //add new refs for user
        for(Group g : groups){
            g.getUsers().add(refUser);
            Ref<Group> refRole = ofy().load().type(Group.class).id(u.getId());
            u.getGroups().add(refRole);
        }
        ofy().save().entities(groups);
        ofy().save().entity(u);
    }

    @Override
    public Long findUserMaxPermissionByRole(IUser user) {
        return null;
    }

    @Override
    public boolean hasUserAccessToRolesId(IUser user, Collection<Long> roles) {
        return false;
    }

    @Override
    public boolean hasUserAccessToRolesByName(IUser user, Collection<String> rolesName) {
        return false;
    }

    @Override
    public boolean hasUserAccessToRoles(IUser user, Collection<IRole> roles) {
        return false;
    }

    @Override
    public boolean hasUserAccessToGroupByName(IUser user, Collection<String> groupsName) {
        return false;
    }

    @Override
    public boolean hasUserAccessToGroups(IUser user, Collection<IGroup> groups) {
        return false;
    }

    @Override
    public IRole newRoleInstance(String roleName, Long permission) {
        Role r = new Role();
        r.setName(roleName);
        r.setPermission(permission);
        return r;
    }

    @Override
    public IGroup newGroupInstance(String groupName, Long permission) {
        Group g = new Group();
        g.setName(groupName);
        g.setPermission(permission);
        return g;
    }

    @Override
    public void setUserName(IUser user, String newUserName) {
        User u = ofy().load().type(User.class).id((Long) user.getId()).get();
        u.setName(newUserName);
        ofy().save().entity(u);
    }

    @Override
    public void setPassword(IUser user, String password) {
        User u = ofy().load().type(User.class).id((Long) user.getId()).get();
        u.setPassword(password);
        ofy().save().entity(u);
    }

    @Override
    public void setHint(IUser user, String hint) {
        User u = ofy().load().type(User.class).id((Long) user.getId()).get();
        u.setHint(hint);
        ofy().save().entity(u);
    }

    @Override
    public void setNickName(IUser user, String nickName) {
        User u = ofy().load().type(User.class).id((Long) user.getId()).get();
        u.setNickName(nickName);
        ofy().save().entity(u);
    }

    @Override
    public IUser save(IUser iUser) {
        return saveOrUpdate(iUser);
    }

    @Override
    public void delete(Long id) {
        throw new UnsupportedOperationException("Operation delete entity by id not implemented, to define entity type by id not possible");
    }

    @Override
    public IUser find(Long id) {
        throw new UnsupportedOperationException("Operation find entity by id not implemented, to define entity type by id not possible");
    }

    @Override
    public IUser update(IUser iUser) {
        throw new UnsupportedOperationException("Operation update entity by id not implemented, to define entity type by id not possible");
    }
}
